package com.lecture.junit.tdd.example;

import java.util.Calendar;
import org.junit.Before;
import org.junit.Test;
import com.lecture.junit.tdd.exceptions.*;

import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

public class LogonServiceTest {

    public static final String USER = "brett";
    public static final String PASSWORD = "password";
	public static final String NEW_PASSWORD = "newPassword";
	public static final String WRONG_PASSWORD = "wrongPassword";
	private static final int EXPIRED_LAST_USE_TIME = 46;
	private static final int EXPIRED_PASSWORD_TIME = 31;
	
    IAccount account;
    IAccountRepository accountRepository;
    LoginService service;

    @Before
    public void init() {
        account = mock(IAccount.class);
        when(account.getUserName()).thenReturn(USER);
        
        accountRepository = mock(IAccountRepository.class);
        when(accountRepository.find(anyString())).thenReturn(account);
        
        service = new LoginService(accountRepository);
    }

    public void willPasswordMatch(boolean flag) {
        when(account.passwordMatches(anyString())).thenReturn(flag);
    }

    
    @Test
    public void testLogin_shouldLogin_whenUserNameAndPasswordPassed() {
        willPasswordMatch(true);
        service.login(USER, PASSWORD);
        verify(account, times(1)).setLoggedIn();
    }
    
    @Test
    public void testLogin_shouldNotSetLoggedIn_whenPasswordNotMatches() {
    	willPasswordMatch(false);
    	service.login(USER, PASSWORD);
    	
    	verify(account, never()).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRevokeAccount_whenThreeLoginAttemptsFail() {
        willPasswordMatch(false);

        service.login(USER, WRONG_PASSWORD);
        service.login(USER, WRONG_PASSWORD);
        service.login(USER, WRONG_PASSWORD);

        verify(account).setRevoked();
    }

    @Test
    public void testLogin_shouldNotRevoke_whenOneAccFailsTwoTimesAndOtherFailsOneTime() {
        willPasswordMatch(false);
        IAccount second = mock(IAccount.class);
        when(second.getUserName()).thenReturn("YURA");
        doReturn(false).when(second).passwordMatches(anyString());
        when(accountRepository.find("YURA")).thenReturn(second);

        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);

        service.login("YURA", PASSWORD);
        verify(second, never()).setRevoked();
    }

    @Test(expected = AccountAlreadyLoggedInException.class)
    public void testLogin_shouldNotifyWithException_whenLoggedIndAccTriesToLogin() {
        willPasswordMatch(true);
        doReturn(true).when(account).isLoggedIn();

        service.login(USER, PASSWORD);
    }

    @Test(expected = AccountNotFoundException.class)
    public void testLogin_shouldThrowException_whenAccIsNotFound() {
        doReturn(null).when(accountRepository).find(anyString());

        service.login(USER, PASSWORD);
    }

    @Test(expected = AccountIsRevokedException.class)
    public void testLogin_shouldNotLoginAndThrowException_whenAccRevoked() {
        doReturn(true).when(account).isRevoked();
        willPasswordMatch(true);
        service.login(USER, PASSWORD);

        verify(account, never()).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRestBackToInitialState_afterSuccessfulLogin() {
        willPasswordMatch(false);
        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);
        willPasswordMatch(true);
        service.login(USER, PASSWORD);
        willPasswordMatch(false);
        service.login(USER, PASSWORD);

        verify(account, never()).setRevoked();
    }
    
    
        
    
    //Homework
    
    @Test(expected = BlankFieldException.class)
    public void testLogin_shouldThroBlankFieldException_afterSendingBlankField() {
    	willPasswordMatch(true);
    	service.login(USER, "");
    }

    @Test(expected = PasswordExpiredException.class)
    public void testChangePassword_shouldPasswordInvalid_afterPasswordExpired() {
    	service.login(USER, PASSWORD);
        Calendar date = Calendar.getInstance();
        date.add(Calendar.DAY_OF_YEAR, -EXPIRED_PASSWORD_TIME);
        
        when(account.getPasswordSetDate()).thenReturn(date);
        willPasswordMatch(true);
    	service.login(USER, PASSWORD);
    }
    
    @Test(expected = IncorrectNewPasswordException.class)
    public void testChangePassrod_shouldThrowNewPasswordException_whenUseSomeOfOldPasswordsAgain() {
    	willPasswordMatch(true);
    	service.login(USER, PASSWORD);
    	doReturn(true).when(account).isNewPasswordAlreadyUsed(PASSWORD);
    	service.changePassword(USER, NEW_PASSWORD, PASSWORD);
    }
    
    @Test(expected = AccountNotUsedException.class)
    public void testLogin_shouldThrowAccountNotUsedException_afterNotUsedFor45days() {
    	willPasswordMatch(true);
        Calendar date = Calendar.getInstance();
        date.add(Calendar.DAY_OF_YEAR, -EXPIRED_LAST_USE_TIME);
        when(account.getLastUseDate()).thenReturn(date);
    	service.login(USER, PASSWORD);
    }
    
    @Test
    public void testLogin_shouldRevokeAccount_afterNotUsedFor45days() {
    	willPasswordMatch(true);
    	Calendar date = Calendar.getInstance();
        date.add(Calendar.DAY_OF_YEAR, -EXPIRED_LAST_USE_TIME);
        when(account.getLastUseDate()).thenReturn(date);
        
    	try{
    		service.login(USER, PASSWORD);    	    		
    	} catch(AccountNotUsedException e) {
    	} finally {
       		verify(account).setRevoked();
       	}
    }
    
}
